{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Embracing web standards"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the main reasons why we developed the current notebook web application \n",
    "was to embrace the web technology. \n",
    "\n",
    "By being a pure web application using HTML, JavaScript, and CSS, the Notebook can get \n",
    "all the web technology improvement for free. Thus, as browser support for different \n",
    "media extend, the notebook web app should be able to be compatible without modification. \n",
    "\n",
    "This is also true with performance of the User Interface as the speed of JavaScript VM increases. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The other advantage of using only web technology is that the code of the interface is fully accessible to the end user and is modifiable live.\n",
    "Even if this task is not always easy, we strive to keep our code as accessible and reusable as possible.\n",
    "This should allow us - with minimum effort - development of small extensions that customize the behavior of the web interface. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tampering with the Notebook application"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first tool that is available to you and that you should be aware of are browser \"developers tool\". The exact naming can change across browser and might require the installation of extensions. But basically they can allow you to inspect/modify the DOM, and interact with the JavaScript code that runs the frontend.\n",
    "\n",
    " - In Chrome and Safari, Developer tools are in the menu `View > Developer > JavaScript Console`  \n",
    " - In Firefox you might need to install [Firebug](http://getfirebug.com/)\n",
    " \n",
    "Those will be your best friends to debug and try different approaches for your extensions."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Injecting JS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Using magics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above tools can be tedious for editing edit long JavaScript files. Therefore we provide the `%%javascript` magic. This allows you to quickly inject JavaScript into the notebook. Still the JavaScript injected this way will not survive reloading. Hence, it is a good tool for testing and refining a script.\n",
    "\n",
    "You might see here and there people modifying css and injecting js into the notebook by reading file(s) and publishing them into the notebook.\n",
    "Not only does this often break the flow of the notebook and make the re-execution of the notebook broken, but it also means that you need to execute those cells in the entire notebook every time you need to update the code.\n",
    "\n",
    "This can still be useful in some cases, like the `%autosave` magic that allows you to control the time between each save. But this can be replaced by a JavaScript dropdown menu to select the save interval."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "## you can inspect the autosave code to see what it does.\n",
    "%autosave??"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### custom.js"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To inject JavaScript we provide an entry point: `custom.js` that allows the user to execute and load other resources into the notebook.\n",
    "JavaScript code in `custom.js` will be executed when the notebook app starts and can then be used to customize almost anything in the UI and in the behavior of the notebook.\n",
    "\n",
    "`custom.js` can be found in the `~/.jupyter/custom/custom.js`.  You can share your custom.js with others."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Back to theory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from jupyter_core.paths import jupyter_config_dir\n",
    "jupyter_dir = jupyter_config_dir()\n",
    "jupyter_dir"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and custom js is in "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import os.path\n",
    "custom_js_path = os.path.join(jupyter_dir, 'custom', 'custom.js')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#  my custom js\n",
    "if os.path.isfile(custom_js_path):\n",
    "    with open(custom_js_path) as f:\n",
    "        print(f.read())\n",
    "else:\n",
    "    print(\"You don't have a custom.js file\")  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that `custom.js` is meant to be modified by user. When writing a script, you can define it in a separate file and add a line of configuration into `custom.js` that will fetch and execute the file."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Warning** : even if modification of `custom.js` takes effect immediately after browser refresh (except if browser cache is aggressive), *creating* a file in `static/` directory needs a **server restart**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise :"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " - Create a `custom.js` in the right location with the following content:\n",
    "```javascript\n",
    "alert(\"hello world from custom.js\")\n",
    "```\n",
    "\n",
    " - Restart your server and open any notebook.\n",
    " - Be greeted by custom.js"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Have a look at [default custom.js](https://github.com/jupyter/notebook/blob/4.0.x/notebook/static/custom/custom.js), to see it's content and for more explanation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### For the quick ones : "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We've seen above that you can change the autosave rate by using a magic. This is typically something I don't want to type every time, and that I don't like to embed into my workflow and documents. (readers don't care what my autosave time is). Let's build an extension that allows us to do it.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "foo": true
   },
   "source": [
    "Create a dropdown element in the toolbar (DOM `Jupyter.toolbar.element`), you will need \n",
    "\n",
    "- `Jupyter.notebook.set_autosave_interval(milliseconds)`\n",
    "- know that 1 min = 60 sec, and 1 sec = 1000 ms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```javascript\n",
    "\n",
    "var label = jQuery('<label/>').text('AutoScroll Limit:');\n",
    "var select = jQuery('<select/>')\n",
    "     //.append(jQuery('<option/>').attr('value', '2').text('2min (default)'))\n",
    "     .append(jQuery('<option/>').attr('value', undefined).text('disabled'))\n",
    "\n",
    "     // TODO:\n",
    "     //the_toolbar_element.append(label)\n",
    "     //the_toolbar_element.append(select);\n",
    "     \n",
    "select.change(function() {\n",
    "     var val = jQuery(this).val() // val will be the value in [2]\n",
    "     // TODO\n",
    "     // this will be called when dropdown changes\n",
    "\n",
    "});\n",
    "\n",
    "var time_m = [1,5,10,15,30];\n",
    "for (var i=0; i < time_m.length; i++) {\n",
    "     var ts = time_m[i];\n",
    "                                          //[2]   ____ this will be `val` on [1]  \n",
    "                                          //     | \n",
    "                                          //     v \n",
    "     select.append($('<option/>').attr('value', ts).text(thr+'min'));\n",
    "     // this will fill up the dropdown `select` with\n",
    "     // 1 min\n",
    "     // 5 min\n",
    "     // 10 min\n",
    "     // 10 min\n",
    "     // ...\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### A non-interactive example first"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I like my cython to be nicely highlighted\n",
    "\n",
    "```javascript\n",
    "Jupyter.config.cell_magic_highlight['magic_text/x-cython'] = {}\n",
    "Jupyter.config.cell_magic_highlight['magic_text/x-cython'].reg = [/^%%cython/]\n",
    "```\n",
    "\n",
    "`text/x-cython` is the name of CodeMirror mode name, `magic_` prefix will just patch the mode so that the first line that contains a magic does not screw up the highlighting. `reg`is a list or regular expression that will trigger the change of mode."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Get more documentation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sadly, you will have to read the js source file (but there are lots of comments) and/or build the JavaScript documentation using yuidoc.\n",
    "If you have `node` and `yui-doc` installed:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```bash\n",
    "$ cd ~/jupyter/notebook/notebook/static/notebook/js/\n",
    "$ yuidoc . --server\n",
    "warn: (yuidoc): Failed to extract port, setting to the default :3000\n",
    "info: (yuidoc): Starting YUIDoc@0.3.45 using YUI@3.9.1 with NodeJS@0.10.15\n",
    "info: (yuidoc): Scanning for yuidoc.json file.\n",
    "info: (yuidoc): Starting YUIDoc with the following options:\n",
    "info: (yuidoc):\n",
    "{ port: 3000,\n",
    "  nocode: false,\n",
    "  paths: [ '.' ],\n",
    "  server: true,\n",
    "  outdir: './out' }\n",
    "info: (yuidoc): Scanning for yuidoc.json file.\n",
    "info: (server): Starting server: http://127.0.0.1:3000\n",
    "```\n",
    "\n",
    "and browse http://127.0.0.1:3000 to get documentation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "foo": true
   },
   "source": [
    "#### Some convenience methods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By browsing the documentation you will see that we have some convenience methods that allows us to avoid re-inventing the UI every time :\n",
    "```javascript\n",
    "Jupyter.toolbar.add_buttons_group([\n",
    "        {\n",
    "             'label'   : 'run qtconsole',\n",
    "             'icon'    : 'fa-terminal', // select your icon from \n",
    "                                          // http://fontawesome.io/icons/\n",
    "             'callback': function(){Jupyter.notebook.kernel.execute('%qtconsole')}\n",
    "        }\n",
    "        // add more button here if needed.\n",
    "        ]);\n",
    "```\n",
    "with a [lot of icons] you can select from. \n",
    "\n",
    "[lot of icons]: http://fontawesome.io/icons/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "foo": true
   },
   "source": [
    "## Cell Metadata"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "foo": true
   },
   "source": [
    "The most requested feature is generally to be able to distinguish an individual cell in the notebook, or run a specific action with them.\n",
    "To do so, you can either use `Jupyter.notebook.get_selected_cell()`, or rely on `CellToolbar`. This allows you to register a set of actions and graphical elements that will be attached to individual cells."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell Toolbar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can see some example of what can be done by toggling the `Cell Toolbar` selector in the toolbar on top of the notebook. It provides two default `presets` that are `Default` and `slideshow`. Default allows the user to edit the metadata attached to each cell manually."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we define a function that takes at first parameter an element on the DOM in which to inject UI element. The second element is the cell this element wis registered with. Then we will need to register that function and give it a name.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Register a callback"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%%javascript\n",
    "var CellToolbar = Jupyter.CellToolbar\n",
    "var toggle =  function(div, cell) {\n",
    "     var button_container = $(div)\n",
    "\n",
    "     // let's create a button that shows the current value of the metadata\n",
    "     var button = $('<button/>').addClass('btn btn-mini').text(String(cell.metadata.foo));\n",
    "\n",
    "     // On click, change the metadata value and update the button label\n",
    "     button.click(function(){\n",
    "                 var v = cell.metadata.foo;\n",
    "                 cell.metadata.foo = !v;\n",
    "                 button.text(String(!v));\n",
    "             })\n",
    "\n",
    "     // add the button to the DOM div.\n",
    "     button_container.append(button);\n",
    "}\n",
    "\n",
    " // now we register the callback under the name foo to give the\n",
    " // user the ability to use it later\n",
    " CellToolbar.register_callback('tuto.foo', toggle);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Registering a preset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This function can now be part of many `preset` of the CellToolBar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "foo": true,
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [],
   "source": [
    "%%javascript\n",
    "Jupyter.CellToolbar.register_preset('Tutorial 1',['tuto.foo','default.rawedit'])\n",
    "Jupyter.CellToolbar.register_preset('Tutorial 2',['slideshow.select','tuto.foo'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should now have access to two presets :\n",
    "\n",
    "  - Tutorial 1\n",
    "  - Tutorial 2\n",
    "  \n",
    "And check that the buttons you defined share state when you toggle preset. \n",
    "Also check that the metadata of the cell is modified when you click the button, and that when saved on reloaded the metadata is still available."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try to wrap the all code in a file, put this file in `{jupyter_dir}/custom/<a-name>.js`, and add \n",
    "\n",
    "```\n",
    "require(['custom/<a-name>']);\n",
    "```\n",
    "\n",
    "in `custom.js` to have this script automatically loaded in all your notebooks.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`require` is provided by a [JavaScript library](http://requirejs.org/) that allow you to express dependency. For simple extension like the previous one we directly mute the global namespace, but for more complex extension you could pass a callback to `require([...], <callback>)` call, to allow the user to pass configuration information to your plugin.\n",
    "\n",
    "In Python language, \n",
    "\n",
    "```javascript\n",
    "require(['a/b', 'c/d'], function( e, f){\n",
    "    e.something()\n",
    "    f.something()\n",
    "})\n",
    "```\n",
    "\n",
    "could be read as\n",
    "```python\n",
    "import a.b as e\n",
    "import c.d as f\n",
    "e.something()\n",
    "f.something()\n",
    "```\n",
    "\n",
    "\n",
    "See for example @damianavila [\"ZenMode\" plugin](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/blob/b29c698394239a6931fa4911440550df214812cb/src/jupyter_contrib_nbextensions/nbextensions/zenmode/main.js#L32) :\n",
    "\n",
    "```javascript\n",
    "\n",
    "// read that as\n",
    "// import custom.zenmode.main as zenmode\n",
    "require(['custom/zenmode/main'],function(zenmode){\n",
    "    zenmode.background('images/back12.jpg');\n",
    "})\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### For the quickest"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try to use [the following](https://github.com/ipython/ipython/blob/1.x/IPython/html/static/notebook/js/celltoolbar.js#L367) to bind a dropdown list to `cell.metadata.difficulty.select`. \n",
    "\n",
    "It should be able to take the 4 following values :\n",
    "\n",
    " - `<None>`\n",
    " - `Easy`\n",
    " - `Medium`\n",
    " - `Hard`\n",
    " \n",
    "We will use it to customize the output of the converted notebook depending on the tag on each cell"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# %load soln/celldiff.js"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
